Packages needed for this tutorial:

library(tidyverse)
library(plotly)

Introduction

The R package, plotly enables us to creative beautiful graphs with interactive graphics. The interactivity aspect is useful in instances where the data set we are dealing with includes a lot of text information or when there is not enough space to plot everything (i.e., data looks really small and compressed). By including interactivity on the graph, users can access information easier, for example, hovering over a point in a bubble chart to see the exact values of the coordinates and name of which country, school, etc., the point represents.

There are also lots of parallels between plotly and ggplot in the sense that most of the plots that can be created with ggplot can also be created with plotly, adding interactivity as a unique feature.

The function used to plot graphs in plotly is plot_ly(). The first input to the function has to be the data set you are working with. Then, you can allocate variable names to “visual properties (e.g., x, y, color, etc)”. We will look at how to plot different types of plots using this function shortly1.

Layout

The layout() function is an essential part in plotting graphs in plotly as it will be commonly used. The argument, layout() can add and modify different parts of the layout of your graph, including, setting the title, axis labels, axis range, plot size, etc.

layout(
  plot_ly(mtcars, x = ~mpg, y = ~hp),
  title = "Miles/(US) gallon and horsepower"
)

However, when we start to plot graphs that are more complex, it may be harder to read the code in the above example. Hence, we can use the pipe function |> to make it easier to read and create the same graph, just insert the title into the layout() function as it is the only addition to the layout of our graph2.

mtcars |>
  plot_ly(x = ~mpg, y = ~hp) |>
  layout(
    title = "Miles/(US) gallon and horsepower",
    autosize = FALSE, width = 600, height = 500
  )
## Warning: Specifying width/height in layout() is now deprecated.
## Please specify in ggplotly() or plot_ly()

Basic Charts

Scatterplots

As seen in the previous section, plot_ly() will plot the data as a scatter plot if the data you input for both variables are both quantitative data. Just inputting the data and variables will you give a basic scatter plot (as seen under the section, ‘Layout’).

Another way to plot a scatter plot is by using add_markers(), which is for indicating what kind of plot you want to produce3. However, this may not always be needed because plot_ly() will plot a scatter plot from two quantitative variables without any specification.

mtcars |>
  plot_ly(x = ~mpg, y = ~hp) |>
  add_markers() |>
  layout(title = "Miles/(US) gallon and horsepower")

To customize your scatter plot even further, you’ll have to add the argument marker =4.

mtcars |>
  plot_ly(
    x = ~mpg, y = ~hp,
    marker = list(
      size = 10,
      color = "red",
      opacity = 0.5
    )
  ) |> # Note that plotly uses 'opacity' instead of 'alpha' to adjust the opacity of points!
  layout(title = "Miles/(US) gallon and horsepower") 

Line Charts

Line charts also requires two variables of quantitative data for input. In order to change it to a line chart, we have to add the argument add_lines() to the plot.

mtcars |>
  plot_ly(x = ~mpg, y = ~hp) |>
  add_lines() |>
  layout(title = "Miles/(US) gallon and horsepower")

Bubble Chart

Bubble charts in plotly are based off the same arguments for creating a scatter plot. The only distinguishing detail is that we have to assign a variable to the argument size = within the marker = list5.

mtcars |>
  plot_ly(
    x = ~mpg, y = ~hp,
    marker = list(size = ~wt, opacity = 0.5, color = "red")
  ) |>
  add_markers() |>
  layout(title = "Miles/(US) gallon and horsepower")

ggplotly()

To get a better understanding of how plotly works, we can first compare it with ggplot. Let us first construct a ggplot object. In this example, we make use of the mtcars data and form a scatterplot, plotting mpg on the x-axis and wt on the y-axis.

mtcars <- mtcars
ggplot_example <- ggplot(mtcars, aes(x = mpg, y = wt)) +
  geom_point()

ggplot_example

We can convert ggplot_example into a plotly object by passing it as an argument in ggplotly(). When we hover over the different points, we see the corresponding mpg and wt value for each point. By setting the argument of tooltip to "all", all the aesthetic mappings of a point can be seen when we hover the cursor over the point.

ggplotly(p = ggplot_example, tooltip = "all")

The main difference between ggplotly() and plot_ly() is that we can use ggplot functions when creating the plot that we pass to ggplotly() as it simply converts ggplot objects into plotly objects. On the other hand, if we use plot_ly() in creating our plot, we can only use plotly functions in doing so.

Adding and Updating Traces

A trace is a set of data with specifications for their locations on the plot. We can always add and update plotly objects by using add_trace(). With this method, we can add new features to our plots. For instance, we can add a trace to example_2.

plot_ly(mtcars, x = ~mpg, y = ~wt, type = "scatter", mode = "markers") |>
  add_trace(
    x = c(10, 15, 20), y = c(2, 3, 4), type = "scatter",
    mode = "markers"
  )

We can also start with an empty figure then add traces to this figure. For instance, we can add three bars to an empty figure.

plot_ly() |>
  add_trace(x = c(1, 2, 3), y = c(5, 10, 3), type = "bar")

Subplots with Plotly

Using the function subplot() allows you to style mutliple plots and display them in a singular plotting space.

Subplot() possesses several advantages over the par(mfrow) parameter:

  • Setting par(mfrow) requires you to reset the parameter again after the desired subplots have been arrange, while subplpot()does not.
  • par(mfrow) does not allow you to create plots of different sizes, while subplot() offers the option for you to create multiple custom-sized subplots.

Creating simple subplots

Using the mtcars dataset again, let’s stack two plots on each other. Here, both plots represent the relationship between Miles/(US) gallon and horsepower as a scatterplot and line graph.

mt_scatter <- mtcars |>
  plot_ly(x = ~mpg, y = ~hp) |>
  add_markers() |>
  layout(title = "Miles/(US) gallon and horsepower")

mt_line <- mtcars |>
  plot_ly(x = ~mpg, y = ~hp) |>
  add_lines() |>
  layout(title = "Miles/(US) gallon and horsepower")

# Specify the name of the plots to display together 
subplot(
  mt_scatter, 
  mt_line,
  # use the nrows argument to arrange the subplots 
  nrows = 2,
  # When TRUE, states that both graphs should share X-axis labels
  shareX = TRUE, 
  margin = 0.05
)

Any subsequent plots placed after the above subplot() chunk will not be affected by subplot().

  • The argument nrows allows us to specify the number of rows the plots will be arranged accordingly to. If we would like to place the two plots side-by-side instead of stacking them, we can specify nrows = 1 instead.
  • As we have two plots containing the same set of information, the x-axis can be standardized and displayed on the bottom graph. We set shareX = TRUE for this. You may search ?subplot in the console for the full documentation guide of arguments.

Using subplot() with add_trace()

Subplot() also works with plots with traces created using the add_trace() function.

plot_1 <- plot_ly() |>
  add_trace(x = c(1, 2, 3), y = c(5, 10, 3), type = "bar") 

plot_2 <- plot_ly() |>
  add_trace(x = c(3, 2, 1), y = c(10, 3, 5), type = "bar") 

subplot(plot_1, plot_2, 
  nrows = 1,
  # creates a small margin between the two subplots
  margin = 0.05
) |> 
   layout(
     # this provides the main overarching title shared by the two plots 
     title = "two example plots", 
     # this allows us to add a list of annotations,in this case the titles for the subplots 
     annotations = list( 
    list(x = 0.15 , y = 1, text = "Plot 1", showarrow = F, xref='paper', yref='paper'), 
    list(x = 0.85 , y = 1, text = "Plot 2", showarrow = F, xref='paper', yref='paper')
    ) 
  ) 

One issue to note is that using subplot() with subplots that each contain a title often results in some of the subplot titles going missing in the final plotting space. You can see this issue with the mtcars Miles/(US) gallon and horsepower figure we constructed earlier.

A suggested remedy is included in the above graph.6

It uses the annotation argument under layout() to manually place annotations using x and y coordinates.

  • xref and yref are crucial arguments that set the container x and y refer to, where “container” spans the entire width of the plot. “paper” refers to the width of the plotting area only.7

Creating basic custom-sized suplots

One powerful feature of subplot() is the ability to create custom-sized subplots. This can be useful in informing viewers on which particular subplots act to substantiate more crucial subplots.

Here is an example below. We want to create 2 smaller stacked subplots of equal combined height to that of a larger subplot on the right. To do this, we create multiple vectors that will contain our subplots.

Here are the subplots we will use:

  • The same Miles/(US) gallon and horsepower scatterplot and line graph as the two smaller subplots (which we have titled “mt_scatter” and “mt_line” from the mt_cars dataset).
  • The add_trace plot we created titled “plot_1” as the larger plot on the right.
s1 <- subplot(mt_scatter, mt_line, nrows = 2, shareX = TRUE)
s2 <- subplot(s1, plot_1, margin = 0.05) |>
  # remove the title that automatically takes the title from one of the subplots
  layout(title = NA)
s2

In summary, creating basic custom-sized subplots can be done by placing “smaller” subplots within a vector that nests itself within another vector containing the “larger” subplot.

3D plots

Scatterplots

Interactivity comes in really useful when you have more variables to plot. Adding the third axis is very straightforward, you may simply add a z attribute to plot_ly(). In the plot below, you can clearly see three clusters of iris species.

p <- iris |>
  plot_ly(
    x = ~Sepal.Length,
    y = ~Petal.Length,
    z = ~Petal.Width,
    marker = list(size = 5)
  ) |>
  add_markers(color = ~Species)

p

Plotly enables us to rotate, zoom in/out, and check the coordinates of datapoints. This is something two dimensional non-interactive plots cannot do, and thus Ploly stands as one of the best options when it comes to three dimensional plots.

For other types of 3d plots, you may refer to the Plotly Graphing library8 and look for the appropriate trace functions.

Axes

In 3d plots, axes are embedded in the scene object, but still inside the layout. So directly putting xaxis = list(title = "x") does not yield the desired output. Below is an example of how to use the scene object. Note that the title and legend are outside of the scene object.

p |>
  layout(
    # main title 
    title = "Iris Species",
    # legend 
    legend = list(
      title = list(text = "<b> Species <b>"),
      itemsizing = "constant"
    ),
    # add titles for three axes
    scene = list(
      xaxis = list(title = "Sepal Length (cm)"),
      yaxis = list(title = "Petal Length (cm)"),
      zaxis = list(title = "Petal Width (cm)")
      )
  )